// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Diagnostics.CodeAnalysis;
using ILLink.Shared;
using Mono.Cecil;

namespace Mono.Linker
{
    /// Tracks dependencies created via DynamicDependencyAttribute in trimming.
    /// This is almost identical to DynamicDependencyAttribute, but it holds a
    /// TypeReference instead of a Type, and it has a reference to the original
    /// CustomAttribute for dependency tracing. It is also a place for helper
    /// methods related to the attribute.
    [System.AttributeUsage(System.AttributeTargets.Constructor | System.AttributeTargets.Field | System.AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
    internal sealed class DynamicDependency : Attribute
    {
        public CustomAttribute? OriginalAttribute { get; private set; }
        public DynamicDependency(string memberSignature)
        {
            MemberSignature = memberSignature;
        }

        public DynamicDependency(string memberSignature, TypeReference type)
        {
            MemberSignature = memberSignature;
            Type = type;
        }

        public DynamicDependency(string memberSignature, string typeName, string assemblyName)
        {
            MemberSignature = memberSignature;
            TypeName = typeName;
            AssemblyName = assemblyName;
        }

        public DynamicDependency(DynamicallyAccessedMemberTypes memberTypes, TypeReference type)
        {
            MemberTypes = memberTypes;
            Type = type;
        }

        public DynamicDependency(DynamicallyAccessedMemberTypes memberTypes, string typeName, string assemblyName)
        {
            MemberTypes = memberTypes;
            TypeName = typeName;
            AssemblyName = assemblyName;
        }

        public string? MemberSignature { get; }

        public DynamicallyAccessedMemberTypes MemberTypes { get; }

        public TypeReference? Type { get; }

        public string? TypeName { get; }

        public string? AssemblyName { get; }

        public string? Condition { get; set; }

        public static DynamicDependency? ProcessAttribute(LinkContext context, ICustomAttributeProvider provider, CustomAttribute customAttribute)
        {
            if (!(provider is IMemberDefinition member))
                return null;

            if (!(member is MethodDefinition || member is FieldDefinition))
                return null;

            // Don't honor the Condition until we have figured out the behavior for DynamicDependencyAttribute:
            // https://github.com/dotnet/linker/issues/1231
            // if (!ShouldProcess(context, customAttribute))
            //   return null;

            var dynamicDependency = GetDynamicDependency(customAttribute);
            if (dynamicDependency != null)
                return dynamicDependency;

            context.LogWarning(member, DiagnosticId.DynamicDependencyAttributeCouldNotBeAnalyzed);
            return null;
        }

        static DynamicDependency? GetDynamicDependency(CustomAttribute ca)
        {
            var args = ca.ConstructorArguments;
            if (args.Count < 1 || args.Count > 3)
                return null;

            DynamicDependency? result = args[0].Value switch
            {
                string stringMemberSignature => args.Count switch
                {
                    1 => new DynamicDependency(stringMemberSignature),
                    2 when args[1].Value is TypeReference type => new DynamicDependency(stringMemberSignature, type),
                    3 when args[1].Value is string typeName && args[2].Value is string assemblyName => new DynamicDependency(stringMemberSignature, typeName, assemblyName),
                    _ => null,
                },
                int memberTypes => args.Count switch
                {
                    2 when args[1].Value is TypeReference type => new DynamicDependency((DynamicallyAccessedMemberTypes)memberTypes, type),
                    3 when args[1].Value is string typeName && args[2].Value is string assemblyName => new DynamicDependency((DynamicallyAccessedMemberTypes)memberTypes, typeName, assemblyName),
                    _ => null,
                },
                _ => null,
            };

            result?.OriginalAttribute = ca;

            return result;
        }

        public static bool ShouldProcess(LinkContext context, CustomAttribute ca)
        {
            if (ca.HasProperties && ca.Properties[0].Name == "Condition")
            {
                var condition = ca.Properties[0].Argument.Value as string;
                switch (condition)
                {
                    case "":
                    case null:
                        return true;
                    case "DEBUG":
                        if (!context.KeepMembersForDebugger)
                            return false;

                        break;
                    default:
                        // Don't have yet a way to match the general condition so everything is excluded
                        return false;
                }
            }
            return true;
        }
    }
}
